package com.googlecode.lightest.core

import org.testng.internal.ResultMap
import org.testng.IReporter
import org.testng.IResultMap
import org.testng.ITestContext
import org.testng.ITestResult
import org.testng.Reporter
import org.testng.TestListenerAdapter

/**
 * This class is responsible for wiring task dispatchers to testcases, and
 * recording the results of running tasks. It also updates the report as each
 * test is run. Concrete subclasses must implement IReporter.
 */
abstract class LightestTestListener extends TestListenerAdapter
    implements ITaskListener, IReporter
{
    public static final ATTR_STRATEGY = 'strategy'
    
    Map<ITestResult, List<ITaskResult>> taskResultMap
    IDispatcherAssignmentStrategy strategy
    
    LightestTestListener() {
        taskResultMap = [:]
    }
    
    void setDispatcherAssignmentStrategy(IDispatcherAssignmentStrategy strategy)
    {
        this.strategy = strategy
    }
    
    /**
     * Ensures the list of failed tests maintained by the TestNG TestRunner
     * implementation underlying the ITestContext reflects the actual test
     * result status. We do this in the listener because the list is
     * manipulated by the TestNG Invoker, which we can't get our hands around.
     * 
     * Yes, this is hackish - we're seriously meddling with the TestRunner
     * internals - but we've got to do it!
     * 
     * @param context
     */
    @Override
    void onFinish(ITestContext context) {
        def testRunner = (org.testng.TestRunner) context
        IResultMap passedTests = new ResultMap()
        
        for (testResult in testRunner.m_passedTests.getAllResults()) {
            if (testResult.getStatus() == ITestResult.FAILURE) {
                testRunner.addFailedTest(testResult.getMethod(), testResult)
            }
            else {
                passedTests.addResult(testResult, testResult.getMethod())
            }
        }
        
        if (passedTests.size() != testRunner.m_passedTests.size()) {
            // update the TestRunner internal variable! Yikes!
            testRunner.m_passedTests = passedTests
        }
        
        super.onFinish(context)
        //generateReport(context.getOutputDirectory())
    }
    
    @Override
    void onStart(ITestContext context) {
        super.onStart(context)
        context.setAttribute(ATTR_STRATEGY, strategy)
    }
    
    @Override
    void onTestStart(ITestResult result) {
        super.onTestStart(result)
        taskResultMap[result] = []
    }
    
    /**
     * Instead of simply delegating to the TestListenerAdapter, we first check
     * if the tasks corresponding to the test method were all OK. If not, mark
     * the result as failed, and report failure to the adapter.
     * 
     * This logic is housed here for convenience, because we don't have many
     * opportunities to alter the result status. Assuming this listener
     * implementation precedes all others in the list of listeners maintained
     * by the TestNG instance, the desired status should be propagated
     * correctly.
     * 
     * @param result
     */
    @Override
    void onTestSuccess(ITestResult result) {
        for (taskResult in taskResultMap[result]) {
            if (taskResult.getStatus() != ITaskResult.STATUS_OK) {
                result.setStatus(ITestResult.FAILURE)
                super.onTestFailure(result)
                return
            }
        }
        
        super.onTestSuccess(result)
    }
    
    /**
     * A wrapper for the proper generateReport() method.
     *
     * @param outputDirectory
     */
    void generateReport(String outputDirectory) {
        List<ITestContext> contexts = getTestContexts()
        List<ISuite> suites = contexts.collect { it.getSuite() }
        List<XmlSuite> xmlSuites = suites.collect { it.getXmlSuite() }
        
        generateReport(xmlSuites, suites, outputDirectory)
    }
    
    /**
     * Returns the a mapping of lists of ITaskResult's obtained when achieving
     * an ITestResult.
     */
    Map<ITestResult, List<ITaskResult>> getTaskResultMap() {
        return taskResultMap
    }
    
    /**
     * Adds the ITaskResult to the list kept for each ITestResult. Only root-
     * level results are added to the list; child results are linked through
     * the root.
     *
     * @param taskResult
     */
    void onTaskComplete(ITaskResult taskResult) {
         if (taskResult.parent() == null) {
            // access Reporter's private static method
            ITestResult result = Reporter.getCurrentTestResult()
            taskResultMap[result] << taskResult
         }
    }
}

